package top.truism.auth.config;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import top.truism.auth.annotation.AdminMeta;
import top.truism.auth.annotation.GroupMeta;
import top.truism.auth.annotation.LoginMeta;
import top.truism.auth.annotation.TouristMeta;
import top.truism.auth.enums.UserLevel;
import top.truism.auth.permission.MetaInfo;
import top.truism.auth.permission.PermissionMeta;
import top.truism.auth.permission.PermissionModule;
import top.truism.auth.util.AnnotationUtil;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public final class PermissionMetaCollector implements BeanPostProcessor {

  /**
   * 所有的权限列表
   */
  private Map<String, MetaInfo> metaMap = new ConcurrentHashMap<>();

  /**
   * module -> Map<permission, Set<identity>
   * 同一个权限名称可以位于不同模块或是不同的控制器内
   *
   */
  private Map<String, Map<String, Set<String>>> structuralMeta = new ConcurrentHashMap<>();

  public PermissionMetaCollector() {
  }

  @Override
  public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    return bean;
  }

  @Override
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    Method[] methods = ReflectionUtils.getAllDeclaredMethods(bean.getClass());
    int methodLength = methods.length;
    for (int i = 0; i < methodLength; i++) {
      Method method = methods[i];
      Annotation annotation = AnnotationUtils.findAnnotation(method, AdminMeta.class);
      if(Objects.nonNull(annotation)) {
        AdminMeta adminMeta = (AdminMeta) annotation;
        if(adminMeta.mount()) {
          String permission = StringUtils.isEmpty(adminMeta.value()) ? adminMeta.permission() : adminMeta.value();
          this.putOneMetaInfo(method, permission, adminMeta.module(), UserLevel.Admin);
        }
      }else {
        annotation = AnnotationUtils.findAnnotation(method, GroupMeta.class);
        if(Objects.nonNull(annotation)) {
          GroupMeta groupMeta = (GroupMeta) annotation;
          if(groupMeta.mount()) {
            String permission = StringUtils.isEmpty(groupMeta.value()) ? groupMeta.permission() : groupMeta.value();
            this.putOneMetaInfo(method, permission, groupMeta.module(), UserLevel.Group);
          }
        }else {
          annotation = AnnotationUtils.findAnnotation(method, LoginMeta.class);
          if(Objects.nonNull(annotation)) {
            LoginMeta loginMeta = (LoginMeta) annotation;
            if(loginMeta.mount()) {
              String permission = StringUtils.isEmpty(loginMeta.value()) ? loginMeta.permission() : loginMeta.value();
              this.putOneMetaInfo(method, permission, loginMeta.module(), UserLevel.Login);
            }
          }else {
            annotation = AnnotationUtils.findAnnotation(method, TouristMeta.class);
            if(Objects.nonNull(annotation)) {
              TouristMeta touristMeta = (TouristMeta) annotation;
              if(touristMeta.mount()) {
                String permission = StringUtils.isEmpty(touristMeta.value()) ? touristMeta.permission() : touristMeta.value();
                this.putOneMetaInfo(method, permission, touristMeta.module(), UserLevel.Tourist);
              }
            }else {
              annotation = AnnotationUtils.findAnnotation(method, PermissionMeta.class);
              if(Objects.nonNull(annotation)) {
                PermissionMeta permissionMeta = (PermissionMeta) annotation;
                if(permissionMeta.mount()) {
                  String permission = StringUtils.isEmpty(permissionMeta.value()) ? permissionMeta.permission() : permissionMeta.value();
                  UserLevel userLevel = AnnotationUtil.findRequired(method);
                  this.putOneMetaInfo(method, permission, permissionMeta.module(), userLevel);
                }
              }
            }
          }
        }
      }

    }

    return bean;
  }

  public MetaInfo findMetaInfo(String identity) {
    return metaMap.get(identity);
  }

  public MetaInfo findMetaInfoByPermission(String permission) {
    Collection<MetaInfo> metaInfoCollection = metaMap.values();
    for(MetaInfo metaInfo : metaInfoCollection) {
      if(metaInfo.getPermission().equals(permission)) {
        return metaInfo;
      }
    }
    return null;
  }

  private void putOneMetaInfo(Method method, String permission, String module, UserLevel userLevel) {
    if(StringUtils.isEmpty(module)) {
      PermissionModule permissionModule = AnnotationUtils.findAnnotation(method.getDeclaringClass(), PermissionModule.class);
      if(permissionModule != null) {
        module = StringUtils.isEmpty(permissionModule.value()) ? method.getDeclaringClass().getName() : permissionModule.value();
      }
    }
    Class cl = method.getDeclaringClass();
    String identity = cl.getName() + "#" + method.getName();
    MetaInfo metaInfo = new MetaInfo(identity, permission, module, userLevel);
    // 添加metaInfo到列表
    this.metaMap.put(identity, metaInfo);
    // 添加模块的权限列表到集合中
    this.putMetaIntoStructuralMeta(module, metaInfo);
  }

  private void putMetaIntoStructuralMeta(String identity, MetaInfo metaInfo) {
    String module = metaInfo.getModule();
    String permission = metaInfo.getPermission();

    Map<String, Set<String>> moduleMap = structuralMeta.get(module);
    if(Objects.nonNull(moduleMap)) {
      putIntoModuleMap(moduleMap, identity, permission);
    }else {
      moduleMap = new ConcurrentHashMap<>();
      putIntoModuleMap(moduleMap, identity, permission);
      this.structuralMeta.put(module, moduleMap);
    }
  }

  private void putIntoModuleMap(Map<String, Set<String>> moduleMap, String identity, String permission) {
    Set<String> identities = moduleMap.get(permission);
    if(Objects.nonNull(identities)) {
      identities.add(identity);
    }else {
      identities = new HashSet<>();
      identities.add(identity);
      moduleMap.put(permission, identities);
    }
  }

  public Map<String, MetaInfo> getMetaMap() {
    return metaMap;
  }

  public void setMetaMap(Map<String, MetaInfo> metaMap) {
    this.metaMap = metaMap;
  }

  public Map<String, Map<String, Set<String>>> getStructuralMeta() {
    return structuralMeta;
  }

  public void setStructuralMeta(Map<String, Map<String, Set<String>>> structuralMeta) {
    this.structuralMeta = structuralMeta;
  }
}
